package com.smsc.headend.task.engine.service.asset.impl;

import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.MeterConfigDMO;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Dcu;
import com.smsc.headend.module.asset.entity.DeviceTypeGroup;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dto.ClearLocalCacheDto;
import com.smsc.headend.module.event.entity.MapEventAlarm;
import com.smsc.headend.module.vending.entity.MapVendingDataItem;
import com.smsc.headend.task.engine.mapper.MapEventAlarmMapper;
import com.smsc.headend.task.engine.mapper.MapVendingDataItemMapper;
import com.smsc.headend.task.engine.po.DeviceDetail;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class AssetServiceImpl implements AssetService {
    public static final String METER_LOCALCACHE_BLACKLIST = "meter:localcache:blacklist:";
    @Autowired
    FeignAssetManagementService assetManagementService;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    MapVendingDataItemMapper mapVendingDataItemMapper;
    @Autowired
    MapEventAlarmMapper mapEventAlarmMapper;
    @Autowired
    RedisTemplate<String, byte[]> redisTemplate;
    @Autowired
    AssetService selfService;
    GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();

    LoadingCache<Long, Meter> meterLocalCache = CacheBuilder.newBuilder()
            //设置缓存容器的初始容量为10
            .initialCapacity(2000)
            // maximumSize 设置缓存大小
//            .maximumSize(65535)
            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(50)
            // expireAfterWrite设置写缓存后61秒钟过期
//            .refreshAfterWrite(2, TimeUnit.MINUTES)
            .build(new CacheLoader<Long, Meter>() {
                @Override
                public Meter load(Long meterId) throws Exception {
                    byte[] meter = redisTemplate.execute(new RedisCallback<byte[]>() {
                        @Override
                        public byte[] doInRedis(RedisConnection redisConnection) throws DataAccessException {
                            return redisConnection.get(("device.meter::id:" + meterId).getBytes(StandardCharsets.ISO_8859_1));
                        }
                    });
                    return meter == null ? assetManagementService.getMeterById(meterId) : serializer.deserialize(meter, Meter.class);
                }
            });

    LoadingCache<Long, MeterConfigDMO> meterConfigCache = CacheBuilder.newBuilder()
            //设置缓存容器的初始容量为10
            .initialCapacity(2000)
            // maximumSize 设置缓存大小
//            .maximumSize(65535)
            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(50)
            // expireAfterWrite设置写缓存后61秒钟过期
//            .expireAfterWrite(60, TimeUnit.MINUTES)
            .build(new CacheLoader<Long, MeterConfigDMO>() {
                @Override
                public MeterConfigDMO load(Long meterId) throws Exception {
                    byte[] meterConfig = redisTemplate.execute(new RedisCallback<byte[]>() {
                        @Override
                        public byte[] doInRedis(RedisConnection redisConnection) throws DataAccessException {
                            return redisConnection.get(("device.meter::config:" + meterId).getBytes(StandardCharsets.ISO_8859_1));
                        }
                    });
                    return meterConfig == null ? assetManagementService.getMeterConfigById(meterId) : serializer.deserialize(meterConfig, MeterConfigDMO.class);
                }
            });

    LoadingCache<Long, ComEntity> comEntityLoadingCache = CacheBuilder.newBuilder()
            //设置缓存容器的初始容量为10
            .initialCapacity(2000)
            // maximumSize 设置缓存大小
//            .maximumSize(65535)
            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(50)
            // expireAfterWrite设置写缓存后61秒钟过期
//            .expireAfterAccess(60, TimeUnit.MINUTES)
            .build(new CacheLoader<Long, ComEntity>() {
                @Override
                public ComEntity load(Long comId) throws Exception {
                    byte[] com = redisTemplate.execute(new RedisCallback<byte[]>() {
                        @Override
                        public byte[] doInRedis(RedisConnection redisConnection) throws DataAccessException {
                            return redisConnection.get(("device.com::id:" + comId).getBytes(StandardCharsets.ISO_8859_1));
                        }
                    });
                    return com == null ? assetManagementService.getComById(comId) : serializer.deserialize(com, ComEntity.class);
                }
            });

    LoadingCache<Long, ComConfigDMO> comConfigCache = CacheBuilder.newBuilder()
            //设置缓存容器的初始容量为10
            .initialCapacity(2000)
            // maximumSize 设置缓存大小
//            .maximumSize(65535)
            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(50)
            // expireAfterWrite设置写缓存后61秒钟过期
//            .expireAfterWrite(60, TimeUnit.MINUTES)
            .build(new CacheLoader<Long, ComConfigDMO>() {
                @Override
                public ComConfigDMO load(Long meterId) throws Exception {
                    byte[] comConfig = redisTemplate.execute(new RedisCallback<byte[]>() {
                        @Override
                        public byte[] doInRedis(RedisConnection redisConnection) throws DataAccessException {
                            return redisConnection.get(("device.com::config:" + meterId).getBytes(StandardCharsets.ISO_8859_1));
                        }
                    });
                    return comConfig == null ? assetManagementService.getComConfigById(meterId) : serializer.deserialize(comConfig, ComConfigDMO.class);
                }
            });

//    LoadingCache<Long, ComEntity> comEntityLoadingCache = CacheBuilder.newBuilder()
//            //设置缓存容器的初始容量为10
//            .initialCapacity(2000)
//            // maximumSize 设置缓存大小
////            .maximumSize(65535)
//            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
//            .concurrencyLevel(20)
//            // expireAfterWrite设置写缓存后1个钟过期
//            .build(new CacheLoader<Long, ComEntity>() {
//                @Override
//                public ComEntity load(Long comId) throws Exception {
//                    return assetManagementService.getComById(comId);
//                }
//            });
//
//
//    LoadingCache<Long, Meter> meterLocalCache = CacheBuilder.newBuilder()
//            //设置缓存容器的初始容量为10
//            .initialCapacity(100)
//            // maximumSize 设置缓存大小
//            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
//            .concurrencyLevel(20)
//            // expireAfterWrite设置写缓存后61秒钟过期
//            .build(new CacheLoader<Long, Meter>() {
//                @Override
//                public Meter load(Long meterId) throws Exception {
//                    return assetManagementService.getMeterById(meterId);
//                }
//            });

    LoadingCache<Long, Dcu> dcuLocalCache = CacheBuilder.newBuilder()
            //设置缓存容器的初始容量为10
            .initialCapacity(100)
            // maximumSize 设置缓存大小
            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(20)
            // expireAfterWrite设置写缓存后61秒钟过期
            .build(new CacheLoader<Long, Dcu>() {
                @Override
                public Dcu load(Long dcuId) throws Exception {
                    return assetManagementService.getDcuById(dcuId);
                }
            });


    LoadingCache<String, String> systemParamCache = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build(new CacheLoader<String, String>() {
        @Override
        public String load(String key) throws Exception {
            return assetManagementService.getPropertyValue(key);
        }
    });

    LoadingCache<String, DeviceOperation> dictionaryCache = CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build(new CacheLoader<String, DeviceOperation>() {
        @Override
        public DeviceOperation load(String key) throws Exception {
            String[] info = key.split(":");
            return assetManagementService.getDictionary(Convert.toLong(info[0]), Convert.toInt(info[1]));
        }
    });

    Set<Long> meterIdBlackList = new LinkedHashSet<>();

    LoadingCache<String, MapVendingDataItem> mapMultispeakDataItemLocalCache = CacheBuilder.newBuilder()
            //设置缓存容器的初始容量为10
            .initialCapacity(1000)
            // maximumSize 设置缓存大小
            .maximumSize(1000)
            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(200)
            // expireAfterWrite设置写缓存后30分钟后过期
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .build(new CacheLoader<String, MapVendingDataItem>() {
                @Override
                public MapVendingDataItem load(String key) {
                    String subDataItemId = key.split("_")[0];
                    String type = key.split("_")[1];
                    String lpDataItemId = key.split("_")[2];
                    MapVendingDataItem mapVendingDataItem = mapVendingDataItemMapper.selectOne(Wrappers.<MapVendingDataItem>lambdaQuery()
                            .eq(MapVendingDataItem::getSubDataItemId, subDataItemId)
                            .eq(MapVendingDataItem::getVendingType, type)
                            .eq(MapVendingDataItem::getLpDataItemId, lpDataItemId));
                    return mapVendingDataItem;
                }
            });

    LoadingCache<Integer, MapEventAlarm> mapEventAlarmLocalCache = CacheBuilder.newBuilder()
            //设置缓存容器的初始容量为10
            .initialCapacity(1000)
            // maximumSize 设置缓存大小
            .maximumSize(1000)
            //设置并发级别为20，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(200)
            // expireAfterWrite设置写缓存后300分钟后过期
            .expireAfterWrite(30, TimeUnit.DAYS)
            .build(new CacheLoader<Integer, MapEventAlarm>() {
                @Override
                public MapEventAlarm load(Integer eventType) {
                    return mapEventAlarmMapper.selectById(eventType);
                }
            });

    @Override
    public List<Long> getOnlineComIds() {
        return assetManagementService.getOnlineComEntityIds();
    }

    @Override
    public ComEntity getComById(Long comId) {
        try {
            ComEntity cached = comEntityLoadingCache.getIfPresent(comId);
            if (cached != null) {
                return cached;
            }
            return comEntityLoadingCache.get(comId);
        } catch (Exception e) {
            log.error("getComEntityError:{}", comId, e);
        }
        return null;
    }

    @Override
    public ComConfigDMO getComConfigById(Long comId) {
        try {
            ComConfigDMO cached = comConfigCache.getIfPresent(comId);
            if (cached != null) {
                return cached;
            }
            return comConfigCache.get(comId);
        } catch (Exception e) {
            log.error("get com config error, {}", comId, e);
        }
        return null;
    }

    @Override
    public DeviceDetail getDeviceDetail(Long comId, Long meterId) {
        ComEntity comEntity = this.getComById(comId);
        Long deviceId = null;
        Long modelId = null;
        if (meterId != null) {
            Meter meter = this.getMeterById(meterId);
            if (meter == null) {
                log.error("DeviceDetail: get failed, comId={},meterId={}", comId, meterId);
                return null;
            }
            deviceId = meter.getMeterId();
            modelId = meter.getModelId();
        } else {
            deviceId = comEntity.getDeviceId();
            ComConfigDMO comConfigDMO = selfService.getComConfigById(comId);
            modelId = comConfigDMO.getModelId();
        }

        if (deviceId == null || modelId == null) {
            log.error("DeviceDetail: get failed, comId={}, meterId={}", comId, meterId);
            return null;
        }

        DeviceTypeGroup deviceTypeGroup = redisUtils.getDeviceTypeGroup(modelId, null, assetManagementService);

        DeviceDetail deviceDetail = new DeviceDetail();
        deviceDetail.setDeviceId(deviceId);
        deviceDetail.setDeviceTypeGroup(deviceTypeGroup);
        return deviceDetail;
    }


    @Override
    public Meter getMeterById(Long meterId) {
        try {
            Meter cachedMeter = meterLocalCache.getIfPresent(meterId);
            if (cachedMeter != null) {
                return cachedMeter;
            }
            return meterLocalCache.get(meterId);
        } catch (Exception e) {
            log.error("get Meter fail", e.getMessage());
        }
        return null;
    }

    @Override
    public MeterConfigDMO getMeterConfigById(Long meterId) {
        try {
            MeterConfigDMO cached = meterConfigCache.getIfPresent(meterId);
            if (cached != null) {
                return cached;
            }
            return meterConfigCache.get(meterId);
        } catch (Exception e) {
            log.error("get meter config error, {}", meterId, e);
        }
        return null;
    }

    @Override
    public Dcu getDcuById(Long dcuId) {
        try {
            return dcuLocalCache.get(dcuId);
        } catch (Exception e) {
            log.error("get DCU fail, dcuId:{}", dcuId, e.getMessage());
        }
        return null;
    }

    @Override
    public String getSystemProperty(String key) {
        try {
            return systemParamCache.get(key);
        } catch (Exception e) {
            log.error("get system param fail, {}", key);
        }
        return null;
    }

    @Override
    public DeviceOperation getDictionary(Long id, Integer type) {
        try {
            return dictionaryCache.get(String.format("%s:%s", id, type));
        } catch (Exception e) {
            log.error("getDictionary param fail, id:{},type:{}", id, type);
        }

        return null;
    }

    @Override
    public void clearDcuCacheById(Long dcuId) {
        dcuLocalCache.invalidate(dcuId);
    }

    @Override
    public void clearMeterCacheById(Long meterId) {
        meterLocalCache.invalidate(meterId);
        meterConfigCache.invalidate(meterId);
    }

    @Override
    public MapVendingDataItem getMultispeakDataItem(Long subDataItemId, Integer type, Long lpDataItemId) {
        try {
            return mapMultispeakDataItemLocalCache.get(subDataItemId + "_" + type + "_" + lpDataItemId);
        } catch (ExecutionException e) {
            log.error("get cached multispeak data item {} fail");
        } catch (Exception e) {
            log.info(e.getMessage());
        }
        return null;
    }

    @Override
    public MapEventAlarm getMapEventAlarm(Integer eventType) {
        try {
            return mapEventAlarmLocalCache.get(eventType);
        } catch (ExecutionException e) {
            log.error("get cached map event alarm {} fail");
        } catch (Exception e) {
            log.info(e.getMessage());
        }
        return null;
    }

    @Override
    public void updateComCache(ClearLocalCacheDto dto) {
        if (dto.getComStatus() == null) {
            comEntityLoadingCache.invalidate(dto.getComId());
        } else {
            ComEntity cachedCom = comEntityLoadingCache.getIfPresent(dto.getComId());
            if (cachedCom != null) {
                cachedCom.setComStatus(dto.getComStatus());
                comEntityLoadingCache.put(dto.getComId(), cachedCom);
            }
        }
    }
}
